package top.jnaw.jee.utils.auth;

import com.alibaba.fastjson.JSONObject;
import com.jfinal.core.Controller;
import com.jfinal.kit.PropKit;
import com.jfinal.kit.StrKit;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Formatter;
import java.util.UUID;
import top.jnaw.jee.utils.Cache;
import top.jnaw.jee.utils.Https;

/**
 * Created by neo on 17-6-1.
 */
public class WechatAuth {

  private static final String APPID = PropKit.get("wechat_appid");
  private static final String SECRET = PropKit.get("wechat_secret");

  private static final String URL_AT_FORMMATTER = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code";
  private static final String URL_UI_FORMARTTER = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN";

  // xufj
  private static final String URL_TK_FORMMATTER = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
  private static final String URL_JT_FORMMATTER = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi";

  /**
   * 通过 code 获取用户信息
   *
   * @param controller 当前的控制器，用来读写会话
   * @param code 微信提供的 code 字符串
   */
  public static JSONObject byCode(Controller controller, String code) {
    JSONObject result = new JSONObject();

    // [Neo] 1. pre-verifyCaptcha code value
    if (StrKit.isBlank(code)) {
      result.put("failed", "code is blank");
      return result;
    }

    // [Neo] 2. get access_token & openid by code
    final String at_content = Https
        .get(String.format(URL_AT_FORMMATTER, APPID, SECRET, code));
    if (StrKit.isBlank(at_content)) {
      result.put("failed", "cannot get access_token");
      return result;
    }

    JSONObject json = JSONObject.parseObject(at_content);
    if (json.containsKey("access_token") && json.containsKey("openid") && json
        .containsKey("scope")) {
      if ("snsapi_userinfo".equals(json.getString("scope"))) {
        // [Neo] TODO cache with expires_in
      } else {
        result.put("failed", "not using snsapi_userinfo");
        return result;
      }
    } else {
      result.put("failed", json.getString("errmsg"));
      return result;
    }

    // [Neo] 3. get user info by access_token & openid
    final String userinfo = Https.get(
        String.format(URL_UI_FORMARTTER, json.getString("access_token"),
            json.getString("openid")));
    if (StrKit.isBlank(userinfo)) {
      result.put("failed", "cannot get userinfo");
      return result;
    }

    json = JSONObject.parseObject(userinfo);
    if (json.containsKey("nickname")) {
      result = json;
    } else {
      result.put("failed", json.getString("errmsg"));
      return result;
    }

    return result;
  }

  private static String create_nonce_str() {
    return UUID.randomUUID().toString();
  }

  private static String create_timestamp() {
    return Long.toString(System.currentTimeMillis() / 1000);
  }

  private static String byteToHex(final byte[] hash) {
    Formatter formatter = new Formatter();
    for (byte b : hash) {
      formatter.format("%02x", b);
    }
    String result = formatter.toString();
    formatter.close();
    return result;
  }

  ////////////////////////////////////////////////////////////////////////////////

  /**
   * 返回签名字符串信息 signature
   */
  public JSONObject getSignture(String shareUrl) {
    String signature = "";
    JSONObject result = new JSONObject();
    String strSignature = "";
    String noncestr = create_nonce_str();
    JSONObject jsapi_ticket = getJsapiTicket();
    String ticket = jsapi_ticket.getString("jsapi_ticket");
    String timestamp = create_timestamp();
    strSignature =
        "jsapi_ticket=" + ticket + "&noncestr=" + noncestr + "&timestamp="
            + timestamp + "&url=" + shareUrl;
    try {
      MessageDigest crypt = MessageDigest.getInstance("SHA-1");
      crypt.reset();
      crypt.update(strSignature.getBytes(StandardCharsets.UTF_8));
      signature = byteToHex(crypt.digest());
    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
    }
    result.putIfAbsent("signature", signature);
    result.putIfAbsent("timestamp", timestamp);
    result.putIfAbsent("noncestr", noncestr);
    result.putIfAbsent("appid", APPID);
    return result;
  }

  /**
   * 返回jsapi_ticket JSONObject
   */
  public JSONObject getJsapiTicket() {
    Object access_token = getAccessToken().get("access_token");
    JSONObject result = new JSONObject();

    Object jsapi_ticket = "";
    String startTimestamp = create_timestamp();

    Object cache_timestamp = Cache.get("jt_timestamp");
    if (null != cache_timestamp) {
      jsapi_ticket = Cache.get("jsapi_ticket");
    } else {
      final String at_cont = Https
          .get(String.format(URL_JT_FORMMATTER, access_token));
      Cache.setx("jt_timestamp", startTimestamp, 7200);
      if (StrKit.isBlank(at_cont)) {
        result.put("failed", "cannot get jsapi_ticket");
        return null;
      }
      JSONObject at_json = JSONObject.parseObject(at_cont);

      jsapi_ticket = at_json.get("ticket");
      Cache.setx("jsapi_ticket", jsapi_ticket, 7200);
    }

    System.out.println("jsapi_ticket : " + jsapi_ticket);
    result.putIfAbsent("jsapi_ticket", jsapi_ticket);
    return result;
  }

  /**
   * 返回 access_token'info  JSONObject
   */
  public JSONObject getAccessToken() {
    JSONObject result = new JSONObject();
    Object access_token = "";
    String startTimestamp = create_timestamp();
    Object cache_timestamp = Cache.get("ak_timestamp");
    if (null != cache_timestamp) {
      access_token = Cache.get("access_token");
    } else {
      final String at_cont = Https
          .get(String.format(URL_TK_FORMMATTER, APPID, SECRET));
      Cache.setx("ak_timestamp", startTimestamp, 7200);
      if (StrKit.isBlank(at_cont)) {
        result.put("failed", "cannot get access_token");
        return null;
      }
      JSONObject at_json = JSONObject.parseObject(at_cont);

      access_token = at_json.get("access_token");
      Cache.setx("access_token", access_token, 7200);
    }

    System.out.println("access_token : " + access_token);
    result.putIfAbsent("access_token", access_token);
    return result;
  }
}
